﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Reflection.Emit;
using IOP.Models.Data;
using IOP.Extension;
using System.Linq.Expressions;

namespace IOP.DynimicProxy
{
    /// <summary>
    /// 表单验证代理构建者
    /// </summary>
    public class FormValidationProxyBuilder : IProxyBuilder<IDataValidation>
    {
        /// <summary>
        /// 代理创建用字典
        /// </summary>
        private ConcurrentDictionary<string, ProxyCreaterFunc> ProxyCreateFuncDic = new ConcurrentDictionary<string, ProxyCreaterFunc>();

        /// <summary>
        /// 构建表单验证代理(泛型)
        /// </summary>
        /// <returns></returns>
        public T BuildProxy<T>()
            where T: class, IDataValidation
        {
            try
            {
                Delegate d;
                Type sourceType = typeof(T);
                if (!sourceType.IsInterface) throw new Exception("Please use interface to build proxy");
                string typeName = $"{sourceType.FullName}FormProxy";

                if(ProxyCreateFuncDic.TryGetValue(typeName,out ProxyCreaterFunc func))
                {
                    d = func.ProxyCreateDelegate;
                }
                else
                {
                    var assemblyName = new AssemblyName("DynimicProxy");
                    var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect);
                    var moduleBuilder = assemblyBuilder.DefineDynamicModule("DynimicProxy");
                    var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class, typeof(DataValidation), new Type[] { sourceType });

                    #region 构建构造函数
                    Type objType = typeof(DataValidation);
                    ConstructorInfo objCtor = objType.GetConstructor(new Type[0]); //获取父类构造函数信息
                    var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[0]);
                    ILGenerator ilOfCtor = constructorBuilder.GetILGenerator();
                    ilOfCtor.Emit(OpCodes.Ldarg_0);
                    ilOfCtor.Emit(OpCodes.Call, objCtor);
                    ilOfCtor.Emit(OpCodes.Ret);
                    #endregion

                    #region 构建属性
                    BuildProperty(sourceType.GetProperties(), typeBuilder);
                    #endregion

                    #region 构建Linq树
                    Type retval = typeBuilder.CreateTypeInfo();
                    var newObjE = Expression.Lambda(Expression.New(retval));
                    d = newObjE.Compile();
                    #endregion

                    ProxyCreaterFunc proxyCreater = new ProxyCreaterFunc(retval, d);
                    ProxyCreateFuncDic.AddOrUpdate(typeName, proxyCreater, (key, value) => value);
                }

                object result = d.DynamicInvoke();
                return result as T;
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        /// <summary>
        /// 构建表单验证代理
        /// </summary>
        /// <param name="origin"></param>
        /// <returns></returns>
        public T BuildProxy<T>(T origin)
            where T : class, IDataValidation
        {
            var obj = BuildProxy<T>();
            origin.CopyDataFromSource(obj);
            return obj;
        }

        /// <summary>
        /// 构建属性
        /// </summary>
        /// <param name="properties"></param>
        /// <param name="builder"></param>
        private void BuildProperty(IEnumerable<PropertyInfo> properties, TypeBuilder builder)
        {
            foreach (var property in properties)
            {
                var field = builder.DefineField($"_{property.Name}", property.PropertyType, FieldAttributes.Private);
                var getGetMethod = property.GetGetMethod();
                var getMethod = builder.DefineMethod(getGetMethod.Name,
                    MethodAttributes.Public |
                    MethodAttributes.SpecialName |
                    MethodAttributes.HideBySig |
                    MethodAttributes.Final |
                    MethodAttributes.NewSlot | 
                    MethodAttributes.Virtual, getGetMethod.CallingConvention, getGetMethod.ReturnType, null);

                var getSetMethod = property.GetSetMethod();
                var setMethod = builder.DefineMethod(getSetMethod.Name,
                    MethodAttributes.Public |
                    MethodAttributes.SpecialName |
                    MethodAttributes.HideBySig |
                    MethodAttributes.Final |
                    MethodAttributes.NewSlot | 
                    MethodAttributes.Virtual, getSetMethod.CallingConvention, null, new Type[] { property.PropertyType });

                // get { return this._{property} }
                var ilOfGet = getMethod.GetILGenerator();
                ilOfGet.Emit(OpCodes.Ldarg_0);
                ilOfGet.Emit(OpCodes.Ldfld, field);
                ilOfGet.Emit(OpCodes.Ret);

                /*
                 * set 
                 * {
                 *     if(_{property} == null || value != _{property}) 
                 *     {
                 *         OnDataStampChanged({property}, _{property}, value)
                 *         _{property} = value
                 *         OnPropertyChanged({property});
                 *         ValidateProperty(value, {property})
                 *     }
                 * }
                 * */
                var ilOfSet = setMethod.GetILGenerator();
                Label lbNullOrNotSameTrue = ilOfSet.DefineLabel();

                ilOfSet.Emit(OpCodes.Ldarg_0);
                ilOfSet.Emit(OpCodes.Ldfld, field);
                ilOfSet.Emit(OpCodes.Ldnull);
                ilOfSet.Emit(OpCodes.Ceq);

                ilOfSet.Emit(OpCodes.Nop);
                ilOfSet.Emit(OpCodes.Ldarg_0);
                ilOfSet.Emit(OpCodes.Ldfld, field);
                ilOfSet.Emit(OpCodes.Ldarg_1);
                ilOfSet.Emit(OpCodes.Ceq);
                ilOfSet.Emit(OpCodes.Ldc_I4_0);
                ilOfSet.Emit(OpCodes.Ceq);

                ilOfSet.Emit(OpCodes.Or);
                ilOfSet.Emit(OpCodes.Brtrue, lbNullOrNotSameTrue);
                ilOfSet.Emit(OpCodes.Ret);

                ilOfSet.Emit(OpCodes.Nop);
                ilOfSet.MarkLabel(lbNullOrNotSameTrue);
                ilOfSet.Emit(OpCodes.Ldarg_0);
                ilOfSet.Emit(OpCodes.Ldstr, property.Name);
                ilOfSet.Emit(OpCodes.Ldarg_0);
                ilOfSet.Emit(OpCodes.Ldfld, field);
                ilOfSet.Emit(OpCodes.Ldarg_1);
                ilOfSet.Emit(OpCodes.Callvirt,
                    typeof(DataValidation).GetMethod("OnDataStampChanged",
                    new Type[] { typeof(string), typeof(object), typeof(object) }));
                ilOfSet.Emit(OpCodes.Nop);
                ilOfSet.Emit(OpCodes.Ldarg_0);
                ilOfSet.Emit(OpCodes.Ldarg_1);
                ilOfSet.Emit(OpCodes.Stfld, field);
                ilOfSet.Emit(OpCodes.Ldarg_0);
                ilOfSet.Emit(OpCodes.Ldstr, property.Name);
                ilOfSet.Emit(OpCodes.Callvirt,
                    typeof(DataValidation).GetMethod("OnPropertyChanged",
                    new Type[] { typeof(string) }));
                ilOfSet.Emit(OpCodes.Nop);
                ilOfSet.Emit(OpCodes.Ldarg_0);
                ilOfSet.Emit(OpCodes.Ldarg_1);
                if (property.PropertyType.BaseType == typeof(ValueType))
                    ilOfSet.Emit(OpCodes.Box, property.PropertyType);
                ilOfSet.Emit(OpCodes.Ldstr, property.Name);
                ilOfSet.Emit(OpCodes.Callvirt,
                    typeof(DataValidation).GetMethod("ValidateProperty",
                    new Type[] { typeof(object), typeof(string) }));
                ilOfSet.Emit(OpCodes.Ret);

                var propertyName = builder.DefineProperty(
                      property.Name, PropertyAttributes.HasDefault, property.PropertyType, null);
                propertyName.SetGetMethod(getMethod);
                propertyName.SetSetMethod(setMethod);
                _ = BuildPropertyValidationAttribute(propertyName, property);
            }
        }

        /// <summary>
        /// 设置验证属性标签
        /// </summary>
        /// <param name="propertyBuilder"></param>
        /// <param name="propertyInfo"></param>
        private PropertyBuilder BuildPropertyValidationAttribute(PropertyBuilder propertyBuilder, PropertyInfo propertyInfo)
        {
            foreach (var att in propertyInfo.GetCustomAttributes<ValidationAttribute>())
            {
                switch (att)
                {
                    case RangeAttribute ran:
                        Type numberType = ran.Maximum.GetType();
                        CustomAttributeBuilder range = new CustomAttributeBuilder(
                            ran.GetType().GetConstructor(new Type[] { numberType, numberType }),
                            new object[] { ran.Minimum, ran.Maximum },
                            new PropertyInfo[] { ran.GetType().GetProperty("ErrorMessage") },
                            new object[] { ran.ErrorMessage });
                        propertyBuilder.SetCustomAttribute(range);
                        break;
                    case RequiredAttribute req:
                        CustomAttributeBuilder required = new CustomAttributeBuilder(
                            req.GetType().GetConstructor(new Type[0]),
                            new object[0],
                            new PropertyInfo[] { req.GetType().GetProperty("ErrorMessage") },
                            new object[] { req.ErrorMessage });
                        propertyBuilder.SetCustomAttribute(required);
                        break;
                    case RegularExpressionAttribute reg:
                        CustomAttributeBuilder regular = new CustomAttributeBuilder(
                            reg.GetType().GetConstructor(new Type[] { typeof(string) }),
                            new object[] { reg.Pattern },
                            new PropertyInfo[] { reg.GetType().GetProperty("ErrorMessage") },
                            new object[] { reg.ErrorMessage });
                        propertyBuilder.SetCustomAttribute(regular);
                        break;
                    case StringLengthAttribute str:
                        CustomAttributeBuilder strLength = new CustomAttributeBuilder(
                            str.GetType().GetConstructor(new Type[] { typeof(int) }),
                            new object[] { str.MaximumLength },
                            new PropertyInfo[] { str.GetType().GetProperty("ErrorMessage") },
                            new object[] { str.ErrorMessage });
                        propertyBuilder.SetCustomAttribute(strLength);
                        break;
                }
            }
            return propertyBuilder;
        }
    }
}
